{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# prerequisites\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from torchvision import datasets, transforms\n",
    "from torch.autograd import Variable\n",
    "from torchvision.utils import save_image\n",
    "\n",
    "# Device configuration\n",
    "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "bs = 100\n",
    "\n",
    "# MNIST Dataset\n",
    "transform = transforms.Compose([\n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))])\n",
    "\n",
    "train_dataset = datasets.MNIST(root='./mnist_data/', train=True, transform=transform, download=True)\n",
    "test_dataset = datasets.MNIST(root='./mnist_data/', train=False, transform=transform, download=False)\n",
    "\n",
    "# Data Loader (Input Pipeline)\n",
    "train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=bs, shuffle=True)\n",
    "test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=bs, shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Generator(nn.Module):\n",
    "    def __init__(self, g_input_dim, g_output_dim):\n",
    "        super(Generator, self).__init__()       \n",
    "        self.fc1 = nn.Linear(g_input_dim, 256)\n",
    "        self.fc2 = nn.Linear(self.fc1.out_features, self.fc1.out_features*2)\n",
    "        self.fc3 = nn.Linear(self.fc2.out_features, self.fc2.out_features*2)\n",
    "        self.fc4 = nn.Linear(self.fc3.out_features, g_output_dim)\n",
    "    \n",
    "    # forward method\n",
    "    def forward(self, x): \n",
    "        x = F.leaky_relu(self.fc1(x), 0.2)\n",
    "        x = F.leaky_relu(self.fc2(x), 0.2)\n",
    "        x = F.leaky_relu(self.fc3(x), 0.2)\n",
    "        return torch.tanh(self.fc4(x))\n",
    "    \n",
    "class Discriminator(nn.Module):\n",
    "    def __init__(self, d_input_dim):\n",
    "        super(Discriminator, self).__init__()\n",
    "        self.fc1 = nn.Linear(d_input_dim, 1024)\n",
    "        self.fc2 = nn.Linear(self.fc1.out_features, self.fc1.out_features//2)\n",
    "        self.fc3 = nn.Linear(self.fc2.out_features, self.fc2.out_features//2)\n",
    "        self.fc4 = nn.Linear(self.fc3.out_features, 1)\n",
    "    \n",
    "    # forward method\n",
    "    def forward(self, x):\n",
    "        x = F.leaky_relu(self.fc1(x), 0.2)\n",
    "        x = F.dropout(x, 0.3)\n",
    "        x = F.leaky_relu(self.fc2(x), 0.2)\n",
    "        x = F.dropout(x, 0.3)\n",
    "        x = F.leaky_relu(self.fc3(x), 0.2)\n",
    "        x = F.dropout(x, 0.3)\n",
    "        return torch.sigmoid(self.fc4(x))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# build network\n",
    "z_dim = 100\n",
    "mnist_dim = train_dataset.train_data.size(1) * train_dataset.train_data.size(2)\n",
    "\n",
    "G = Generator(g_input_dim = z_dim, g_output_dim = mnist_dim).to(device)\n",
    "D = Discriminator(mnist_dim).to(device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Generator(\n",
       "  (fc1): Linear(in_features=100, out_features=256, bias=True)\n",
       "  (fc2): Linear(in_features=256, out_features=512, bias=True)\n",
       "  (fc3): Linear(in_features=512, out_features=1024, bias=True)\n",
       "  (fc4): Linear(in_features=1024, out_features=784, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "G"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Discriminator(\n",
       "  (fc1): Linear(in_features=784, out_features=1024, bias=True)\n",
       "  (fc2): Linear(in_features=1024, out_features=512, bias=True)\n",
       "  (fc3): Linear(in_features=512, out_features=256, bias=True)\n",
       "  (fc4): Linear(in_features=256, out_features=1, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "D"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# loss\n",
    "criterion = nn.BCELoss() \n",
    "\n",
    "# optimizer\n",
    "lr = 0.0002 \n",
    "G_optimizer = optim.Adam(G.parameters(), lr = lr)\n",
    "D_optimizer = optim.Adam(D.parameters(), lr = lr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def D_train(x):\n",
    "    #=======================Train the discriminator=======================#\n",
    "    D.zero_grad()\n",
    "\n",
    "    # train discriminator on real\n",
    "    x_real, y_real = x.view(-1, mnist_dim), torch.ones(bs, 1)\n",
    "    x_real, y_real = Variable(x_real.to(device)), Variable(y_real.to(device))\n",
    "\n",
    "    D_output = D(x_real)\n",
    "    D_real_loss = criterion(D_output, y_real)\n",
    "    D_real_score = D_output\n",
    "\n",
    "    # train discriminator on facke\n",
    "    z = Variable(torch.randn(bs, z_dim).to(device))\n",
    "    x_fake, y_fake = G(z), Variable(torch.zeros(bs, 1).to(device))\n",
    "\n",
    "    D_output = D(x_fake)\n",
    "    D_fake_loss = criterion(D_output, y_fake)\n",
    "    D_fake_score = D_output\n",
    "\n",
    "    # gradient backprop & optimize ONLY D's parameters\n",
    "    D_loss = D_real_loss + D_fake_loss\n",
    "    D_loss.backward()\n",
    "    D_optimizer.step()\n",
    "        \n",
    "    return  D_loss.data.item()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "def G_train(x):\n",
    "    #=======================Train the generator=======================#\n",
    "    G.zero_grad()\n",
    "\n",
    "    z = Variable(torch.randn(bs, z_dim).to(device))\n",
    "    y = Variable(torch.ones(bs, 1).to(device))\n",
    "\n",
    "    G_output = G(z)\n",
    "    D_output = D(G_output)\n",
    "    G_loss = criterion(D_output, y)\n",
    "\n",
    "    # gradient backprop & optimize ONLY G's parameters\n",
    "    G_loss.backward()\n",
    "    G_optimizer.step()\n",
    "        \n",
    "    return G_loss.data.item()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1/200]: loss_d: 0.612, loss_g: 4.823\n",
      "[2/200]: loss_d: 0.433, loss_g: 7.166\n",
      "[3/200]: loss_d: 0.659, loss_g: 4.955\n",
      "[4/200]: loss_d: 0.960, loss_g: 5.740\n",
      "[5/200]: loss_d: 0.981, loss_g: 5.142\n",
      "[6/200]: loss_d: 0.830, loss_g: 4.728\n",
      "[7/200]: loss_d: 1.088, loss_g: 2.881\n",
      "[8/200]: loss_d: 0.903, loss_g: 4.148\n",
      "[9/200]: loss_d: 0.939, loss_g: 3.857\n",
      "[10/200]: loss_d: 0.915, loss_g: 2.447\n",
      "[11/200]: loss_d: 0.725, loss_g: 2.648\n",
      "[12/200]: loss_d: 0.606, loss_g: 3.450\n",
      "[13/200]: loss_d: 0.674, loss_g: 2.825\n",
      "[14/200]: loss_d: 0.710, loss_g: 2.358\n",
      "[15/200]: loss_d: 0.610, loss_g: 2.900\n",
      "[16/200]: loss_d: 0.602, loss_g: 3.156\n",
      "[17/200]: loss_d: 0.683, loss_g: 2.690\n",
      "[18/200]: loss_d: 0.596, loss_g: 2.868\n",
      "[19/200]: loss_d: 0.617, loss_g: 2.786\n",
      "[20/200]: loss_d: 0.516, loss_g: 3.059\n",
      "[21/200]: loss_d: 0.665, loss_g: 2.887\n",
      "[22/200]: loss_d: 0.682, loss_g: 2.943\n",
      "[23/200]: loss_d: 0.637, loss_g: 2.670\n",
      "[24/200]: loss_d: 0.598, loss_g: 2.759\n",
      "[25/200]: loss_d: 0.648, loss_g: 2.476\n",
      "[26/200]: loss_d: 0.711, loss_g: 2.519\n",
      "[27/200]: loss_d: 0.704, loss_g: 2.523\n",
      "[28/200]: loss_d: 0.720, loss_g: 2.350\n",
      "[29/200]: loss_d: 0.721, loss_g: 2.343\n",
      "[30/200]: loss_d: 0.675, loss_g: 2.424\n",
      "[31/200]: loss_d: 0.746, loss_g: 2.195\n",
      "[32/200]: loss_d: 0.780, loss_g: 2.146\n",
      "[33/200]: loss_d: 0.806, loss_g: 2.022\n",
      "[34/200]: loss_d: 0.750, loss_g: 2.043\n",
      "[35/200]: loss_d: 0.798, loss_g: 1.983\n",
      "[36/200]: loss_d: 0.821, loss_g: 1.901\n",
      "[37/200]: loss_d: 0.816, loss_g: 1.943\n",
      "[38/200]: loss_d: 0.787, loss_g: 1.953\n",
      "[39/200]: loss_d: 0.798, loss_g: 1.954\n",
      "[40/200]: loss_d: 0.814, loss_g: 1.919\n",
      "[41/200]: loss_d: 0.797, loss_g: 1.921\n",
      "[42/200]: loss_d: 0.830, loss_g: 1.822\n",
      "[43/200]: loss_d: 0.832, loss_g: 1.810\n",
      "[44/200]: loss_d: 0.847, loss_g: 1.797\n",
      "[45/200]: loss_d: 0.835, loss_g: 1.838\n",
      "[46/200]: loss_d: 0.859, loss_g: 1.743\n",
      "[47/200]: loss_d: 0.847, loss_g: 1.795\n",
      "[48/200]: loss_d: 0.878, loss_g: 1.702\n",
      "[49/200]: loss_d: 0.890, loss_g: 1.664\n",
      "[50/200]: loss_d: 0.866, loss_g: 1.708\n",
      "[51/200]: loss_d: 0.895, loss_g: 1.653\n",
      "[52/200]: loss_d: 0.904, loss_g: 1.635\n",
      "[53/200]: loss_d: 0.906, loss_g: 1.650\n",
      "[54/200]: loss_d: 0.907, loss_g: 1.608\n",
      "[55/200]: loss_d: 0.910, loss_g: 1.619\n",
      "[56/200]: loss_d: 0.922, loss_g: 1.591\n",
      "[57/200]: loss_d: 0.925, loss_g: 1.576\n",
      "[58/200]: loss_d: 0.929, loss_g: 1.580\n",
      "[59/200]: loss_d: 0.926, loss_g: 1.580\n",
      "[60/200]: loss_d: 0.942, loss_g: 1.536\n",
      "[61/200]: loss_d: 0.948, loss_g: 1.519\n",
      "[62/200]: loss_d: 0.960, loss_g: 1.520\n",
      "[63/200]: loss_d: 0.969, loss_g: 1.477\n",
      "[64/200]: loss_d: 0.972, loss_g: 1.494\n",
      "[65/200]: loss_d: 0.967, loss_g: 1.479\n",
      "[66/200]: loss_d: 0.986, loss_g: 1.466\n",
      "[67/200]: loss_d: 0.978, loss_g: 1.464\n",
      "[68/200]: loss_d: 0.973, loss_g: 1.485\n",
      "[69/200]: loss_d: 0.980, loss_g: 1.461\n",
      "[70/200]: loss_d: 0.991, loss_g: 1.436\n",
      "[71/200]: loss_d: 0.981, loss_g: 1.449\n",
      "[72/200]: loss_d: 0.972, loss_g: 1.481\n",
      "[73/200]: loss_d: 0.990, loss_g: 1.453\n",
      "[74/200]: loss_d: 0.989, loss_g: 1.451\n",
      "[75/200]: loss_d: 0.977, loss_g: 1.469\n",
      "[76/200]: loss_d: 0.988, loss_g: 1.456\n",
      "[77/200]: loss_d: 0.982, loss_g: 1.459\n",
      "[78/200]: loss_d: 0.977, loss_g: 1.472\n",
      "[79/200]: loss_d: 0.994, loss_g: 1.439\n",
      "[80/200]: loss_d: 0.987, loss_g: 1.447\n",
      "[81/200]: loss_d: 0.989, loss_g: 1.458\n",
      "[82/200]: loss_d: 0.995, loss_g: 1.439\n",
      "[83/200]: loss_d: 0.989, loss_g: 1.460\n",
      "[84/200]: loss_d: 0.995, loss_g: 1.455\n",
      "[85/200]: loss_d: 1.003, loss_g: 1.415\n",
      "[86/200]: loss_d: 1.002, loss_g: 1.424\n",
      "[87/200]: loss_d: 0.996, loss_g: 1.430\n",
      "[88/200]: loss_d: 0.996, loss_g: 1.437\n",
      "[89/200]: loss_d: 1.007, loss_g: 1.421\n",
      "[90/200]: loss_d: 0.992, loss_g: 1.438\n",
      "[91/200]: loss_d: 1.001, loss_g: 1.422\n",
      "[92/200]: loss_d: 1.004, loss_g: 1.411\n",
      "[93/200]: loss_d: 0.999, loss_g: 1.440\n",
      "[94/200]: loss_d: 1.007, loss_g: 1.400\n",
      "[95/200]: loss_d: 0.999, loss_g: 1.422\n",
      "[96/200]: loss_d: 1.007, loss_g: 1.416\n",
      "[97/200]: loss_d: 1.004, loss_g: 1.412\n",
      "[98/200]: loss_d: 0.999, loss_g: 1.434\n",
      "[99/200]: loss_d: 1.009, loss_g: 1.409\n",
      "[100/200]: loss_d: 1.007, loss_g: 1.432\n",
      "[101/200]: loss_d: 1.010, loss_g: 1.402\n",
      "[102/200]: loss_d: 1.011, loss_g: 1.401\n",
      "[103/200]: loss_d: 1.015, loss_g: 1.405\n",
      "[104/200]: loss_d: 1.004, loss_g: 1.415\n",
      "[105/200]: loss_d: 1.004, loss_g: 1.422\n",
      "[106/200]: loss_d: 1.005, loss_g: 1.413\n",
      "[107/200]: loss_d: 1.003, loss_g: 1.418\n",
      "[108/200]: loss_d: 0.999, loss_g: 1.419\n",
      "[109/200]: loss_d: 0.999, loss_g: 1.446\n",
      "[110/200]: loss_d: 1.006, loss_g: 1.410\n",
      "[111/200]: loss_d: 1.000, loss_g: 1.417\n",
      "[112/200]: loss_d: 1.002, loss_g: 1.417\n",
      "[113/200]: loss_d: 0.993, loss_g: 1.425\n",
      "[114/200]: loss_d: 0.997, loss_g: 1.432\n",
      "[115/200]: loss_d: 1.005, loss_g: 1.424\n",
      "[116/200]: loss_d: 1.004, loss_g: 1.416\n",
      "[117/200]: loss_d: 1.013, loss_g: 1.403\n",
      "[118/200]: loss_d: 1.003, loss_g: 1.409\n",
      "[119/200]: loss_d: 0.999, loss_g: 1.423\n",
      "[120/200]: loss_d: 0.999, loss_g: 1.423\n",
      "[121/200]: loss_d: 0.995, loss_g: 1.422\n",
      "[122/200]: loss_d: 1.001, loss_g: 1.423\n",
      "[123/200]: loss_d: 1.000, loss_g: 1.429\n",
      "[124/200]: loss_d: 0.995, loss_g: 1.437\n",
      "[125/200]: loss_d: 0.992, loss_g: 1.436\n",
      "[126/200]: loss_d: 0.992, loss_g: 1.425\n",
      "[127/200]: loss_d: 0.992, loss_g: 1.431\n",
      "[128/200]: loss_d: 0.989, loss_g: 1.437\n",
      "[129/200]: loss_d: 0.995, loss_g: 1.431\n",
      "[130/200]: loss_d: 0.992, loss_g: 1.438\n",
      "[131/200]: loss_d: 0.982, loss_g: 1.460\n",
      "[132/200]: loss_d: 0.984, loss_g: 1.455\n",
      "[133/200]: loss_d: 0.989, loss_g: 1.447\n",
      "[134/200]: loss_d: 0.989, loss_g: 1.432\n",
      "[135/200]: loss_d: 0.980, loss_g: 1.462\n",
      "[136/200]: loss_d: 0.979, loss_g: 1.455\n",
      "[137/200]: loss_d: 0.982, loss_g: 1.451\n",
      "[138/200]: loss_d: 0.983, loss_g: 1.455\n",
      "[139/200]: loss_d: 0.977, loss_g: 1.448\n",
      "[140/200]: loss_d: 0.981, loss_g: 1.460\n",
      "[141/200]: loss_d: 0.972, loss_g: 1.464\n",
      "[142/200]: loss_d: 0.973, loss_g: 1.478\n",
      "[143/200]: loss_d: 0.972, loss_g: 1.461\n",
      "[144/200]: loss_d: 0.971, loss_g: 1.481\n",
      "[145/200]: loss_d: 0.969, loss_g: 1.493\n",
      "[146/200]: loss_d: 0.967, loss_g: 1.465\n",
      "[147/200]: loss_d: 0.973, loss_g: 1.487\n",
      "[148/200]: loss_d: 0.966, loss_g: 1.458\n",
      "[149/200]: loss_d: 0.966, loss_g: 1.485\n",
      "[150/200]: loss_d: 0.960, loss_g: 1.485\n",
      "[151/200]: loss_d: 0.960, loss_g: 1.485\n",
      "[152/200]: loss_d: 0.954, loss_g: 1.518\n",
      "[153/200]: loss_d: 0.956, loss_g: 1.510\n",
      "[154/200]: loss_d: 0.955, loss_g: 1.493\n",
      "[155/200]: loss_d: 0.955, loss_g: 1.501\n",
      "[156/200]: loss_d: 0.947, loss_g: 1.517\n",
      "[157/200]: loss_d: 0.949, loss_g: 1.539\n",
      "[158/200]: loss_d: 0.947, loss_g: 1.501\n",
      "[159/200]: loss_d: 0.945, loss_g: 1.526\n",
      "[160/200]: loss_d: 0.940, loss_g: 1.521\n",
      "[161/200]: loss_d: 0.934, loss_g: 1.563\n",
      "[162/200]: loss_d: 0.941, loss_g: 1.529\n",
      "[163/200]: loss_d: 0.935, loss_g: 1.549\n",
      "[164/200]: loss_d: 0.938, loss_g: 1.541\n",
      "[165/200]: loss_d: 0.930, loss_g: 1.562\n",
      "[166/200]: loss_d: 0.924, loss_g: 1.549\n",
      "[167/200]: loss_d: 0.926, loss_g: 1.583\n",
      "[168/200]: loss_d: 0.930, loss_g: 1.564\n",
      "[169/200]: loss_d: 0.927, loss_g: 1.548\n",
      "[170/200]: loss_d: 0.917, loss_g: 1.577\n",
      "[171/200]: loss_d: 0.924, loss_g: 1.565\n",
      "[172/200]: loss_d: 0.916, loss_g: 1.590\n",
      "[173/200]: loss_d: 0.913, loss_g: 1.607\n",
      "[174/200]: loss_d: 0.915, loss_g: 1.577\n",
      "[175/200]: loss_d: 0.913, loss_g: 1.582\n",
      "[176/200]: loss_d: 0.911, loss_g: 1.583\n",
      "[177/200]: loss_d: 0.908, loss_g: 1.626\n",
      "[178/200]: loss_d: 0.900, loss_g: 1.604\n",
      "[179/200]: loss_d: 0.905, loss_g: 1.613\n",
      "[180/200]: loss_d: 0.905, loss_g: 1.625\n",
      "[181/200]: loss_d: 0.901, loss_g: 1.618\n",
      "[182/200]: loss_d: 0.894, loss_g: 1.631\n",
      "[183/200]: loss_d: 0.887, loss_g: 1.650\n",
      "[184/200]: loss_d: 0.888, loss_g: 1.640\n",
      "[185/200]: loss_d: 0.887, loss_g: 1.648\n",
      "[186/200]: loss_d: 0.886, loss_g: 1.636\n",
      "[187/200]: loss_d: 0.878, loss_g: 1.663\n",
      "[188/200]: loss_d: 0.881, loss_g: 1.627\n",
      "[189/200]: loss_d: 0.881, loss_g: 1.642\n",
      "[190/200]: loss_d: 0.877, loss_g: 1.656\n",
      "[191/200]: loss_d: 0.872, loss_g: 1.692\n",
      "[192/200]: loss_d: 0.871, loss_g: 1.674\n",
      "[193/200]: loss_d: 0.873, loss_g: 1.671\n",
      "[194/200]: loss_d: 0.869, loss_g: 1.663\n",
      "[195/200]: loss_d: 0.863, loss_g: 1.701\n",
      "[196/200]: loss_d: 0.865, loss_g: 1.685\n",
      "[197/200]: loss_d: 0.865, loss_g: 1.682\n",
      "[198/200]: loss_d: 0.855, loss_g: 1.714\n",
      "[199/200]: loss_d: 0.846, loss_g: 1.708\n",
      "[200/200]: loss_d: 0.853, loss_g: 1.727\n"
     ]
    }
   ],
   "source": [
    "n_epoch = 200\n",
    "for epoch in range(1, n_epoch+1):           \n",
    "    D_losses, G_losses = [], []\n",
    "    for batch_idx, (x, _) in enumerate(train_loader):\n",
    "        D_losses.append(D_train(x))\n",
    "        G_losses.append(G_train(x))\n",
    "\n",
    "    print('[%d/%d]: loss_d: %.3f, loss_g: %.3f' % (\n",
    "            (epoch), n_epoch, torch.mean(torch.FloatTensor(D_losses)), torch.mean(torch.FloatTensor(G_losses))))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "with torch.no_grad():\n",
    "    test_z = Variable(torch.randn(bs, z_dim).to(device))\n",
    "    generated = G(test_z)\n",
    "\n",
    "    save_image(generated.view(generated.size(0), 1, 28, 28), './samples/sample_' + '.png')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
